﻿using System;
using Microsoft.Xna.Framework;
using XNAGameEngine.Logs;

namespace XNAGameEngine.GameState
{
    /// <summary>
    /// Manage the registration, switching, updating and transitioning between game states
    /// </summary>
    public sealed class ManagerGameState : DrawableGameComponent, IManagerGameState
    {
        #region local fields

        //the transition between game states
        private GameStateTransition _currentTransition;

        //registry object to store all registered transitions and their string value associations
        private GameStateTransitionRegistry _transitionRegistry;

        //registry object to store all registered states and their string value associations
        private GameStateRegistry _stateRegistry;

        //the current state
        private GameState _currentState;

        //the string value of the state intially loaded on startup and returns to when the game is reset
        private string _initialStateName;

        //the state incoming state replacing the current state
        private string _incomingStateName;

        #endregion

        #region constructors

        /// <summary>
        /// Constructor to initialize all local members and store the game engine
        /// </summary>
        public ManagerGameState(Game game)
            : base(game)
        {
            game.Services.AddService(typeof(IManagerGameState), this);
            game.Components.Add(this);
            _stateRegistry = new GameStateRegistry(this);
            _transitionRegistry = new GameStateTransitionRegistry(this);
            _incomingStateName = null;
            ManagerLogs.Write("ManagerGameState : Created");
        }

        #endregion

        #region local methods

        /// <summary>
        /// Registers an object type with an string value. The first state registered
        /// will automatically be assigned to the intial state. This can be changed with the
        /// "SetInitialState() call"
        /// </summary>
        /// <param name="key">the string value to register with the object type</param>
        /// <param name="value">the object type to register with the string value</param>
        public void RegisterGameStateObject(string key, Type value)
        {
            //register the state with the string value. 
            _stateRegistry.RegisterGameStateObject(key, value);

            if (_initialStateName == null)
                SetInitialState(key);
        }

        /// <summary>
        /// Set the state the game loads when started up and reset
        /// </summary>
        /// <param name="key">the string value registered to the intial state</param>
        public void SetInitialState(string key)
        {
            //check if the key is registered first
            if (!_stateRegistry.KeyIsRegistered(key))
                throw new SystemException("String value is not registered.\n File: StateMachine.cs"
                    + "\n Method: SetInitialState()");

            _initialStateName = key;
        }

        /// <summary>
        /// <para>Set the type of transition between states.</para>
        /// <para>The default transition is fade to black.</para>
        /// </summary>
        /// <param name="transition">the transition type</param>
        public void SetGameStateTransition(GameStateTransition transition)
        {
            _currentTransition = transition;
        }

        /// <summary>
        /// Initialize the engine
        /// </summary>
        public override void Initialize()
        {
            //set the default state transition
            if (_currentTransition == null)
                _currentTransition = _transitionRegistry.GetGameStateTransition("FadeToBlack");

            if (_currentState != null)
                throw new SystemException("Cannot Initialize GameStateManager multiple times. Try using"
                    + " ResetEngine() instead");

            //set the default gamestate and initialize it
            _currentState = _stateRegistry.CreateStateInstance(_initialStateName);
            _currentState.Initialize();
            base.Initialize();
        }

        /// <summary>
        /// Update the current GameStateObject and/or transition to a new one
        /// </summary>
        /// <param name="gameTime">a snapshot of timing values</param>
        public override void Update(GameTime gameTime)
        {
            if (_currentState._GSOState != GSState.INACTIVE)
                _currentState.Update(gameTime);
            base.Update(gameTime);
        }

        /// <summary>
        /// <para>Draw the current GameStateObject and/or change the state if the transition out is complete.</para>
        /// <para>Ensures that the transition gets drawn after the game state to prevent objects from being
        /// drawn on top of the transition</para>
        /// </summary>
        /// <param name="gameTime">a snapshot of timing values</param>
        public override void Draw(GameTime gameTime)
        {
            switch (_currentState._GSOState)
            {
                case GSState.ACTIVE:
                    _currentState.Draw(gameTime);
                    break;

                case GSState.INACTIVE:
                    break;

                case GSState.LOADING:
                    _currentState.Draw(gameTime);
                    if (_currentTransition.TransitionIn(gameTime))
                    {
                        _currentState.Resume();
                    }
                    break;

                case GSState.UNLOADING:
                    _currentState.Draw(gameTime);
                    if (_currentTransition.TransitionOut(gameTime))
                    {
                        _currentState.Cleanup();
                        _currentState = _stateRegistry.CreateStateInstance(_incomingStateName);
                        _currentState.Initialize();
                        _incomingStateName = null;
                    }
                    break;
                default:
                    throw new SystemException("Unknown GSOState. \n  File: GameStateManager.cs\nMethod: Draw()");
            }
            base.Draw(gameTime);
        }

        #endregion

        #region IManagerGameState methods

        /// <summary>
        /// Gets the string reference of the current state
        /// </summary
        public string GetCurrentStateName { get { return _currentState._stringReference; } }

        /// <summary>
        /// Gets whether or not the current state is in its active state
        /// </summary>
        public bool IsCurrentStateActive { get { return _currentState._GSOState == GSState.ACTIVE; } }

        /// <summary>
        /// Called by StateObjects to reset the engine back to the initial state
        /// </summary>
        public void ResetEngine()
        {
            ChangeState(_initialStateName);
        }

        /// <summary>
        /// Called by StateObjects to change the engine to a new state
        /// </summary>
        /// <param name="newState">string value of the new state</param>
        public void ChangeState(string newState)
        {
            _currentState.PreCleanup();
            _stateRegistry.DestroyStateInstance(_currentState._GSOType);
            _incomingStateName = newState;
        }

        /// <summary>
        /// Called by StateObjects to change the engine to a new state, using a different transition
        /// </summary>
        /// <param name="newState">string value of the new state</param>
        /// <param name="transition">string value of the transition to use</param>
        public void ChangeState(string newState, string transition)
        {
            _currentTransition = _transitionRegistry.GetGameStateTransition(transition);
            ChangeState(newState);
        }

        /// <summary>
        /// Called by StateObjects to cleanup and shut down the state engine. NOT IMPLENTED YET
        /// </summary>
        public void ShutdownEngine()
        {
            Game.Exit();
        }

        /// <summary>
        /// Registers an object type with an string value. The first state registered
        /// will automatically be assigned to the intial state. This can be changed with the
        /// "SetInitialState() call"
        /// </summary>
        /// <param name="key">the string value to register with the object type</param>
        /// <param name="value">the object type to register with the string value</param>
        public void RegisterGameState(string key, Type value)
        {
            //register the state with the string value. 
            _stateRegistry.RegisterGameStateObject(key, value);

            if (_initialStateName == null)
                SetInitialState(key);
        }

        /// <summary>
        /// Registers an GameStateTransition object type with a string value. 
        /// </summary>
        /// <param name="key">the string to register with the object type</param>
        /// <param name="value">the object type to register with the string</param>
        public void RegisterGameStateTransition(string key, Type value)
        {
            //register the state with the string value. 
            _transitionRegistry.RegisterGameStateTransition(key, value);
        }

        #endregion
    }
}
